/*
 * Copyright 2020-2025 the original author or authors.
 * You cannot use this file unless authorized by the author.
 */
package org.ipig.commons.service.dyna;

import lombok.extern.slf4j.Slf4j;

import javax.tools.*;
import java.io.ByteArrayOutputStream;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * DynaClassCompile
 *
 * @author <a href="mailto:comchnts@163.com">chinats</a>
 * @version $$Id: DynaClassCompile.java 3 2020-01-21 09:36:33Z ts <comchnts@163.com> $$
 * @since 1.0
 */
@Slf4j
public class DynaClassCompile {
    public Map<String, byte[]> compiler(final String javaSrcCode, final String javaFileName) {
        Map<String, byte[]> clazzBytesMap = new HashMap<>();// clazzBytesMap 就是存储字节码的容器，它的键为类名，值为类的字节码数组,当要使用编译后的类时，直接使用类加载器加载 clazzBytesMap 中的字节码即可
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
        try {
            JavaFileManager javaFileManager = buildOutJavaFileManager(fileManager, clazzBytesMap);
            JavaFileObject srcJavaFileObject = new CustomJavaFileObject(JavaFileObject.Kind.SOURCE, javaFileName, javaSrcCode);
            List<JavaFileObject> compilationUnits = new ArrayList<>();
            compilationUnits.add(srcJavaFileObject);
            JavaCompiler.CompilationTask task = compiler.getTask(null, javaFileManager, null,
                    null, null, compilationUnits);
            boolean flag = task.call();
            if (!flag) {
                log.warn("规则文件名name=【{}】,编译失败，规则内容rule=【{}】", javaFileName, javaSrcCode);
            }
        } finally {
            try {
                fileManager.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return clazzBytesMap;
    }


    /**
     * 从源文件到内存的编译方式
     *
     * @param fileManager
     * @param bytes
     * @return
     */
    public JavaFileManager buildOutJavaFileManager(StandardJavaFileManager fileManager, Map<String, byte[]> bytes) {
        JavaFileManager jfm = new ForwardingJavaFileManager<StandardJavaFileManager>(fileManager) {
            public JavaFileObject getJavaFileForOutput(Location location,
                                                       String className,
                                                       JavaFileObject.Kind kind,
                                                       FileObject sibling) throws IOException {
                if (kind == JavaFileObject.Kind.CLASS) {
                    return new SimpleJavaFileObject(URI.create(className + ".class"), JavaFileObject.Kind.CLASS) {
                        public OutputStream openOutputStream() {
                            return new FilterOutputStream(new ByteArrayOutputStream()) {
                                public void close() throws IOException {
                                    out.close();
                                    ByteArrayOutputStream bos = (ByteArrayOutputStream) out;
                                    bytes.put(className, bos.toByteArray());
                                }
                            };
                        }
                    };
                } else {
                    return super.getJavaFileForOutput(location, className, kind, sibling);
                }
            }
        };
        return jfm;
    }
}
